/**
 * Copyright (C) 2009+ Spark Zheng
 *
 * @file        src/utils.c 
 * @date        2010-04-05
 * @brief	general_network
 * @see         dserver
 * @history
 */

#include <string.h>
#include <stdio.h>
#include <dlib/global.h>

#include "utils.h"

int dserver_server_new(const char *domain, in_port_t port, prot_type_t type)
{
	int sock = 0;
	struct addrinfo hints;
	struct addrinfo *ai;
	struct addrinfo *next;
	char port_str[INET_PORTSTRLEN] = {0};

	memset(&hints, 0, sizeof(hints));
	hints.ai_flags = AI_PASSIVE;
	hints.ai_family = AF_INET;

	if (type == PROTOCOL_TCP) {
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_protocol = IPPROTO_TCP;
	} else if (type == PROTOCOL_UDP) {
		hints.ai_socktype = SOCK_DGRAM;
		hints.ai_protocol = IPPROTO_UDP;
	} else {
		/* error protocol type */
		return -E_UTILS_PROTOCOL;
	}

	if (port != 0) {
		snprintf(port_str, INET_PORTSTRLEN, "%u", port); 
		if (getaddrinfo(domain, port_str, &hints, &ai) != 0) {
			return -E_UTILS_NETDB; 
		}
	} else {
		if (getaddrinfo(domain, NULL, &hints, &ai) != 0) {
			return -E_UTILS_NETDB;
		}
	}

	for (next = ai; next != NULL; next = next->ai_next) {
		/* create socket */
		if ((sock = socket(next->ai_family, next->ai_socktype,
				   next->ai_protocol)) < 0) {
			sock = -E_UTILS_SOCKET;
			continue;
		}

		/* set general socketoptions */
		if (dserver_set_nonblock(sock) < 0) {
			close(sock);
			sock = -E_UTILS_SOCKOPT;
			continue;
		}
		if (dserver_set_reuseaddr(sock) < 0) {
			close(sock);
			sock = -E_UTILS_SOCKOPT;
			continue;
		}
		/* TODO: sndbuf/rcvbuf */

		/* bind addr */
		if (bind(sock, next->ai_addr, next->ai_addrlen) < 0) {
			close(sock);
			sock = -E_UTILS_BIND;
			continue;
		}

		/* listen port */
		if (next->ai_socktype == SOCK_STREAM) {
			if (listen(sock, DSERVER_BACKLOG_LEN) < 0) {
				close(sock);
				sock = -E_UTILS_LISTEN;
				continue;
			}
		}

		break;
	}
	freeaddrinfo(ai);

	return sock;
}

/** client */
int dserver_tcp_client_new(const char *domain, in_port_t port, uint16_t timeo)
{
	int ret = 0;
	int sock = 0;
	struct addrinfo hints;
	struct addrinfo *ai;
	struct addrinfo *next;
	char port_str[INET_PORTSTRLEN] = {0};

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;

	snprintf(port_str, INET_PORTSTRLEN, "%u", port); 
	if (getaddrinfo(domain, port_str, &hints, &ai) != 0) {
		return -E_UTILS_NETDB; 
	}

	for (next = ai; next != NULL; next = next->ai_next) {
		/* create socket */
		if ((sock = socket(next->ai_family, next->ai_socktype,
				   next->ai_protocol)) < 0) {
			sock = -E_UTILS_SOCKET;
			continue;
		}

#ifndef DSERVER_BLOCK_CLIENT 
		/* set general socketoptions */
		if (dserver_set_nonblock(sock) < 0) {
			close(sock);
			sock = -E_UTILS_SOCKOPT;
			continue;
		}
#endif
		/* TODO: sndbuf/rcvbuf */

		/* connect to server, udp could improve performance */
		if (connect(sock, next->ai_addr, next->ai_addrlen) < 0) {
			if (errno == EINPROGRESS) {
				ret = dserver_wait_write(sock, timeo);	
				if (ret < 0) {
					close(sock);
					sock = ret;
					continue;
				}
			} else {
				close(sock);
				sock = -E_UTILS_CONNECT;
				continue;
			} /* end errno */
		}

		break;
	}
	freeaddrinfo(ai);

	return sock;
}

int dserver_udp_client_new(const char *domain, in_port_t port, uint16_t timeo)
{
	int ret = 0;
	int sock = 0;
	struct addrinfo hints;
	struct addrinfo *ai;
	struct addrinfo *next;
	char port_str[INET_PORTSTRLEN] = {0};

	memset(&hints, 0, sizeof(hints));
	hints.ai_family = AF_INET;

	/** non-connected udp */
	if (domain == NULL || port == 0) {
		hints.ai_socktype = SOCK_DGRAM;
		hints.ai_protocol = IPPROTO_UDP;

		if ((sock = socket(hints.ai_family, hints.ai_socktype,
				   hints.ai_protocol)) < 0) {
			return -E_UTILS_SOCKET;
		}

		/* set general socketoptions */
		if (dserver_set_nonblock(sock) < 0) {
			close(sock);
			return -E_UTILS_SOCKOPT;
		}
		/* TODO: sndbuf/rcvbuf */	

		return sock;
	}

	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_protocol = IPPROTO_UDP;

	snprintf(port_str, INET_PORTSTRLEN, "%u", port); 
	if (getaddrinfo(domain, port_str, &hints, &ai) != 0) {
		return -E_UTILS_NETDB; 
	}

	for (next = ai; next != NULL; next = next->ai_next) {
		/* create socket */
		if ((sock = socket(next->ai_family, next->ai_socktype,
				   next->ai_protocol)) < 0) {
			sock = -E_UTILS_SOCKET;
			continue;
		}

#ifndef DSERVER_BLOCK_CLIENT 
		/* set general socketoptions */
		if (dserver_set_nonblock(sock) < 0) {
			close(sock);
			sock = -E_UTILS_SOCKOPT;
			continue;
		}
#endif
		/* TODO: sndbuf/rcvbuf */

		/* connect to server, udp could improve performance */
		if (connect(sock, next->ai_addr, next->ai_addrlen) < 0) {
			if (errno == EINPROGRESS) {
				ret = dserver_wait_write(sock, timeo);	
				if (ret < 0) {
					close(sock);
					sock = ret;
					continue;
				}
			} else {
				close(sock);
				sock = -E_UTILS_CONNECT;
				continue;
			} /* end errno */
		}

		break;
	}
	freeaddrinfo(ai);

	return sock;
}


/** tcp data functions */
ssize_t dserver_data_write(int sockfd, const char *buf, size_t buf_len, uint16_t timeo)
{
	int err;
	ssize_t ret = 0;
	size_t nwritten = 0;

	while (nwritten < buf_len) {
		ret = write(sockfd, buf + nwritten, buf_len - nwritten);
		if (ret < 0) {
			err = errno;
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
			if (err == EWOULDBLOCK) {
				ret = dserver_wait_write(sockfd, timeo);
				if (ret < 0) {
					return ret;
				} else {
					continue;
				}
			}
#endif
			if (err == EAGAIN) {
				ret = dserver_wait_write(sockfd, timeo);
				if (ret < 0) {
					return ret;
				} else {
					continue;
				}
			} else if (err == EINTR) {
				continue;
			} else {
				return -E_NETWORK_WRITE;
			}
		} else if (ret == 0) {
			return -E_NETWORK_RST;
		} else {
			nwritten += ret;
		} /* if ret */
	} /* while */

	return (ssize_t)nwritten;
}

ssize_t dserver_data_writev(int sockfd, const struct iovec *vec,
		uint16_t vec_nr, uint16_t timeo)
{
	int err;
	uint16_t i;
	int nready;
	uint16_t cur_block = 0;
	size_t cur_offset = 0;
	uint32_t cur_countbytes = 0;
	ssize_t nwritten = 0;
	ssize_t ret;
	int write_again = 1;
	struct pollfd pollfd;
	struct iovec new_vector[DSERVER_IOV_MAX];
	struct iovec *new_vector_startp = NULL;
	uint16_t new_vector_len = 0;
	ssize_t vec_base_byte_ordinal[DSERVER_IOV_MAX];

	if (vec == NULL || vec_nr <= 0 || vec_nr > DSERVER_IOV_MAX) {
		return -E_ARGS_ILLEGAL;
	}

	/**
	 * copy the const struct to a non-const struct of iovec 
	 * and then compute the byte ordinal of each vector base
	 */
	for (i = 0; i < vec_nr; i++) {
		new_vector[i].iov_base = vec[i].iov_base;
		new_vector[i].iov_len = vec[i].iov_len;

		vec_base_byte_ordinal[i] = 0;
		if (i >= 1) {
			vec_base_byte_ordinal[i] += 
				(vec_base_byte_ordinal[i - 1] + vec[i].iov_len);
		} else {
			vec_base_byte_ordinal[i] += vec[i].iov_len;
		}
	}

	/** now we writev until all data has been written */
	while (cur_block < vec_nr) {
		/** proccess the situation of part of the iov_base be sent */
		if (write_again == 1) {
			new_vector_startp = &(new_vector[cur_block]);
			new_vector_len = vec_nr - cur_block;
			new_vector[cur_block].iov_base = (void *)((char *)new_vector[cur_block].iov_base + cur_offset);
			new_vector[cur_block].iov_len = new_vector[cur_block].iov_len - cur_offset;
		}

		write_again = 1;
		ret = writev(sockfd, new_vector_startp, new_vector_len);
		if (ret < 0) {
			err = errno;
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
			if (err == EWOULDBLOCK) {
				pollfd.fd = sockfd;
				pollfd.events = POLLWRNORM;
				nready = poll(&pollfd, 1, timeo);
				if (nready == 0) {	
					return -E_EVENT_NODATA;
				}
				write_again = 0;
				continue;
			}
#endif
			if (err == EAGAIN) {
				pollfd.fd = sockfd;
				pollfd.events = POLLWRNORM;
				nready = poll(&pollfd, 1, timeo);
				if (nready == 0) {	
					return -E_EVENT_NODATA;
				}
				write_again = 0;
				continue;
			} else if (err == EINTR) {
				write_again = 0;
				continue;
			} else {
				return -E_NETWORK_WRITE;
			}
		} /* ret < 0 */

		/* search block and offset by written bytes */
		nwritten += ret;
		cur_block = 0;
		cur_countbytes = 0;
		cur_offset = 0;
		for (i = 0; i < vec_nr; i++) {
			if (nwritten < vec_base_byte_ordinal[i]) {
				cur_offset = vec[i].iov_len - (vec_base_byte_ordinal[i] - nwritten);
				break;
			} else if (nwritten == vec_base_byte_ordinal[i]) {
				cur_block++;
				cur_offset = 0;
				break;
			} else {
				cur_block++;
			}
		} /* end for */
	} /* end while */

	return nwritten;
}

ssize_t dserver_data_read(int sockfd, char *buf, size_t buf_len, uint16_t timeo)
{
	int err;
	ssize_t ret = 0;
	size_t nreaded = 0;

	ret = dserver_wait_read(sockfd, timeo);
	if (ret < 0) {
		return ret;
	}

	while (nreaded < buf_len) {
		ret = read(sockfd, buf + nreaded, buf_len - nreaded);
		if (ret < 0) {
			err = errno;
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
			if (err == EWOULDBLOCK) {
				break;
			}
#endif
			if (err == EAGAIN) {
				break;
			} else if (err == EINTR) {
				continue;
			} else {
				return -E_NETWORK_READ;
			}
		} else if (ret == 0) { /* FIN */
			break;
		} else {
			nreaded += ret;
		} /* if ret */
	} /* while buf_len > nreaded */

	return (ssize_t)nreaded;
}
	
ssize_t dserver_data_readv(int sockfd, struct iovec *vec, uint16_t vec_nr, uint16_t timeo)
{
	UNUSED_PARAM(sockfd);
	UNUSED_PARAM(vec);
	UNUSED_PARAM(vec_nr);
	UNUSED_PARAM(timeo);
	return 0;
}

ssize_t dserver_data_sendfile(int infd, int outfd, size_t nr, off_t *from, off_t *to)
{
	UNUSED_PARAM(infd);
	UNUSED_PARAM(outfd);
	UNUSED_PARAM(nr);
	UNUSED_PARAM(from);
	UNUSED_PARAM(to);
	return 0;
}
